﻿using System;
using System.Globalization;
using System.Text.RegularExpressions;
using QAToolsAddin.GenericDialogs;
using QAToolsAddin.GenericDialogs.Fields;
using Excel = Microsoft.Office.Interop.Excel;

namespace QAToolsAddin.UI {
    class Metric {
        private readonly Excel.Application _excelapp;
        private Excel.Range _range;
        private CultureInfo _culture;

        public Metric(Excel.Application application, CultureInfo culture) {
            try {
                _excelapp = application;
                _culture = culture;
                _range = application.ActiveWindow.RangeSelection;
            } catch (Exception ex) { new FrmException(_excelapp, ex).ShowDialog(); }
        }

        public void ToUnitAngle() {
            ToUnit("Angle unit conversion", -1, "General \"{0}\"", new Item[]{
                //Reference : Radian
                new Item("Degree", "deg", x=> x / 180d * 3.14159265358979,  x=> x / 3.14159265358979 * 180d ),
                new Item("Radian", "rad", x => x, x => x ) 
            });
        }

        public void ToUnitFlow() {
            ToUnit("Flow unit conversion", -1, "General \"{0}\"", new Item[]{
                //Reference : Liter / min
                new Item("Cube inch per hour", "in³/hr", 3661.4246457),
                new Item("Cube inch per minute", "in³/min", 61.023744095),
                new Item("Cube inch per second", "in³/sec", 1.017062402),
                new Item("Cubic centimeter per hour", "cm³/hr", 60000),
                new Item("Cubic centimeter per minute", "cm³/min", 1000),
                new Item("Cubic centimeter per second", "cm³/sec", 16.666666667),
                new Item("Cubic Foot per hour", "ft³/hr", 2.118880003),
                new Item("Cubic Foot per minute", "ft³/min", 0.035314667),
                new Item("Cubic Foot per second", "ft³/sec", 0.000588578),
                new Item("Cubic meter per hour", "m³/hr", 0.06),
                new Item("Cubic meter per minute", "m³/min", 0.001),
                new Item("Cubic meter per second", "m³/sec", 0.000016667),
                new Item("Cubic millimeter per hour", "mm³/hr", 60000000),
                new Item("Cubic millimeter per minute", "mm³/min", 1000000),
                new Item("Cubic millimeter per second", "mm³/sec", 16666.66667),
                new Item("Imp Gallon per hour", "gal/hr", 13.198154898),
                new Item("Imp Gallon per minute", "gal/min", 0.219969248),
                new Item("Imp Gallon per second", "gal/sec", 0.003666154),
                new Item("Liter per hour", "L/hr", 60),
                new Item("Liter per minute", "L/min", 1d),
                new Item("Liter per second", "L/sec", 0.016666667),
                new Item("US Gallon per hour", "gal/hr", 15.850323141),
                new Item("US Gallon per minute", "gal/min", 0.264172052),
                new Item("US Gallon per second", "gal/sec", 0.004402868),
            });
        }

        public void ToUnitGeoCoordinate() {
            int index = -1;
            string firstTxt = (string)((Excel.Range)_range.Cells[1, 1]).Text;
            if (firstTxt.IndexOf("°") != -1) {
                if (firstTxt.IndexOf("'", 3) != -1) {
                    if (firstTxt.IndexOf("\"", 6) != -1) index = 0;
                    else index = 1;
                } else index = 2;
            } else if (Regex.IsMatch(firstTxt, @"^[-.\d]+\s+[-.\d]+$")) index = 3;
            else if (Regex.IsMatch(firstTxt, @"^\d+[A-Z]\s+\d+\s+\d+$")) index = 4;
            ToUnit("Geo coordinates unit convesion", index, "@", new Item[]{
                //Reference : LL
                new Item("Degree Minute Second 12°15'35\"N ...", "", Coordinates.LL.TryParseDMS, x => x, x => ((Coordinates.LL)x).ToDMS().ToStringDMS() ),
                new Item("Degree Minute Decimal 12°15.3675'N ...", "", Coordinates.LL.TryParseDMS, x => x, x => ((Coordinates.LL)x).ToDMS().ToStringDM() ),
                new Item("Degree Decimal 12.4323°S 23.5434°E", "", Coordinates.LL.TryParseDMS, x => x, x => ((Coordinates.LL)x).ToDMS().ToStringD() ),
                new Item("Full Decimal -12.43233 23.65453", "", Coordinates.LL.TryParseLL, x => x, x => ((Coordinates.LL)x).ToString() ),
                new Item("UTM 13U 65465 65465", "", Coordinates.LL.TryParseUTM, x => x, x => ((Coordinates.LL)x).ToUTM().ToString() ),
            });
        }

        public void ToUnitLength() {
            ToUnit("Length unit conversion", -1, "General \"{0}\"", new Item[]{
                //Reference : Meter
                new Item("Meter","m", 1),
                new Item("Millimeter", "mm", 1000),
                new Item("Centimeter", "cm", 100),
                new Item("Kilometer", "km", 0.001),
                new Item("Foot", "ft", 3.28084),
                new Item("Inch", "in", 39.37008 ),
                new Item("Mile", "mi", 0.00062),
                new Item("See mile", "nm", 0.00054),
                new Item("Yard", "yd", 1.09361),
            });
        }

        public void ToUnitNumber() {
            int index = ((Excel.Range)_range.Cells[1, 1]).Value is double ? 0 : -1;
            ToUnit("Length unit conversion", index, "{0}", new Item[]{
                //Reference : Decimal
                new Item("Decimal", "", Parser.ToIdentical, x => x, x => x ),
                new Item("Hexadecimal", "\"0x\"@", Parser.ToNumberFromHexa, x => x, x => Parser.ToHexa((long)(double)x) ),
                new Item("Binary", "", Parser.ToNumberFromBinary, x => x, x => Parser.ToBinary((int)(double)x) ),
            });
        }

        public void ToUnitPressure() {
            ToUnit("Pressure unit conversion", -1, "General \"{0}\"", new Item[]{
                //Reference : Kgf/Cm²
                new Item("Atmosphere", "atm", 0.9678413),
                new Item("Bar", "bar", 0.09806652),
                new Item("KegaPascals", "KPa", 98.0665205),
                new Item("Kg/Cm²", "kgf/cm²", 1),
                new Item("Kg/m²", "kgf/cm²", 10000),
                new Item("MegaPascals ", "MPa", 0.0980665),
                new Item("Millibar", "mbar", 980.6652048),
                new Item("Pound per square foot", "psf", 2048.1614489),
                new Item("Pound per square inch", "psi", 14.22334258),
            });
        }

        public void ToUnitArea() {
            ToUnit("Area unit conversion", -1, "General \"{0}\"", new Item[]{
                //Reference : Square meter
                new Item("Acre", "acre", 0.0002471055),
                new Item("Hectare", "ha", 0.0001),
                new Item("Square centimeter", "cm²", 10000),
                new Item("Square Foot", "ft²", 10.76391),
                new Item("Square inch", "in²", 1550.003),
                new Item("Square meter", "m²", 1d),
                new Item("Square millimeter", "mm²", 1000000),
                new Item("Square kilimeter", "km²", 0.000001),
                new Item("Square mile", "mi²", 0.0000003861022),
                new Item("Square yard", "yd²", 1.19599)
            });
        }

        public void ToUnitTemperature() {
            ToUnit("Temperature unit convesion", -1, "General \"{0}\"", new Item[]{
                //Reference : Kelvin
                new Item("Celsius", "°C", x=> x + 273.15d, x => x - 273.15d ),
                new Item("Fahrenheit", "°F", x =>(x + 459.67d) / 1.8d, x => x * 1.8d - 459.67d ),
                new Item("Kelvin", "K", x => x, x => x),
            });
        }

        public void ToUnitSpeed() {
            ToUnit("Speed unit convesion", -1, "General \"{0}\"", new Item[]{
                //Reference : meter per second
                new Item("kilometer per second", "km/s", 0.001),
                new Item("kilometer per minute", "km/min", 0.06),
                new Item("kilometer per hour", "km/hr", 3.6),
                new Item("meter per second", "m/s", 1d),
                new Item("meter per minute", "m/min", 60),
                new Item("meter per hour", "m/hr", 3600),
                new Item("mile per second", "mps", 0.0006213712),
                new Item("mile per minute", "mpm", 0.0372822715),
                new Item("mile per hour", "mph", 2.23693629 ),
                new Item("foot per second", "fps", 3.2808399 ),
                new Item("foot per minute", "fpm", 196.850394),
                new Item("foot per hour", "fph", 11811.0236),
                new Item("Knot", "knot", 1.943845),
                new Item("minute per kilometer", "min/km", x=>16.67d/x, x=>16.67d/x ),
                new Item("second per kilometer", "sec/km", x=>1000d/x, x=>1000d/x ),
                new Item("second per 100 metres", "sec/hm", x=>100d/x, x=>100d/x ),
                new Item("minute per mile", "min/mi", x=>26.82d/x, x=>26.82d/x ),
                new Item("second per mile", "sec/mi", x=>1609d/x, x=>1609d/x ),
                new Item("second per 100 yards", "sec/yd", x=>91.44d/x, x=>91.44d/x ),
            });
        }

        public void ToUnitTime() {
            ToUnit("Time unit convesion", -1, "General \"{0}\"", new Item[]{
                //Reference : day
                new Item("Millisecond", "ms", 86400000),
                new Item("Second", "s", 86400),
                new Item("Minute", "min", 1440),
                new Item("Hour", "hr", 24),
                new Item("Day", "day", 1d),
            });
        }

        public void ToUnitVolume() {
            ToUnit("Volume unit convesion", -1, "General \"{0}\"", new Item[]{
                //Reference : Litre
                new Item("Cubic centimeter", "cm³", 1000),
                new Item("Cubic decimeter", "dm³", 1),
                new Item("Cubic Foot", "ft³", 0.0353146667),
                new Item("Cubic inch", "in³", 61.0237441),
                new Item("Cubic meter", "m³", 0.001),
                new Item("Cubic yard", "yd³", 0.00130795062),
                new Item("Hectoliter", "hl", 0.01),
                new Item("Imp. Gallon", "gal", 0.219969157),
                new Item("Imp. Pint", "gal", 1.75975326),
                new Item("Imp. Quart", "gal", 0.87987663),
                new Item("Litre", "L", 1),
                new Item("US Gallon", "gal", 0.264172052),
                new Item("US Pint", "pint", 2.11337642),
                new Item("US Quart", "qt", 1.05668821),
            });
        }

        public void ToUnitWeigth() {
            ToUnit("Weigth unit convesion", -1, "General \"{0}\"", new Item[]{
                //Reference : Kilogram
                new Item("Gram", "g", 1000),
                new Item("Hectogram", "hg", 10),
                new Item("Kilogram", "kg", 1d),
                new Item("Milligram", "mg", 1000000),
                new Item("Ounce", "oz", 35.2739619),
                new Item("Pound", "lb", 2.20462262),
                new Item("US Long ton", "long ton", 1),
                new Item("US Short ton", "short ton", 1.1199997155),
            });
        }

        private void ToUnit(string title, int indexFrom, string format, Item[] Items) {
            if (indexFrom == -1) {
                string firstFormat = (string)((Excel.Range)_range.Cells[1, 1]).NumberFormat;
                if (firstFormat != "General" && firstFormat != "@") {
                    for (int i = 0; i < Items.Length; i++) {
                        if (firstFormat == string.Format(format, Items[i].Format)) {
                            indexFrom = i;
                            break;
                        }
                    }
                }
            }
            var f0 = new FieldList<Item>("From unit", "From unit", Items, indexFrom, true);
            var f1 = new FieldList<Item>("To unit", "To unit", Items, -1, true);
            var f2 = new FieldNumber("Rounding", "Number of digit after the decimal point (empty=disabled)", (double?)null, 0, 15);
            var f3 = new FieldCheckBox("Add symbol", "Add the currency symbol", true, true);
            new InputBox(_excelapp, title, f0, f1, f2, f3).Show(delegate {
                Item ItemFrom = (Item)f0.Value;
                Item ItemTo = (Item)f1.Value;
                int precision = (int)(f2.Value ?? -1);
                string cellFormat = f3.Value ? string.Format(format, ItemTo.Format) : "";
                new CellsOperator(_range).ForEachValue(cellFormat, delegate(ref object value) {
                    ItemFrom.ToUnit(ItemTo, _culture, ref value);
                    if (value is double && precision != -1)
                        value = Math.Round((double)value, precision);
                });
            });
        }

        private class Item {
            public delegate bool ParseDelegate(object x, out object ret);
            public delegate object FormulaDelegate(double x);
            public delegate object ConvertDelegate(object x);

            private ParseDelegate _parse;
            private ConvertDelegate _toBase;
            private ConvertDelegate _toUnit;

            public string Name { get; private set; }
            public string Format { get; private set; }

            public Item(string name, string format, double rate)
                : this(name, format, x => (double)x / rate, x => (double)x * rate) {
            }

            public Item(string name, string format, FormulaDelegate toBase, FormulaDelegate toUnit)
                : this(name, format, Parser.Number, x => toBase((double)x), x => toUnit((double)x)) {
            }

            public Item(string name, string format, ParseDelegate parse, ConvertDelegate toBase, ConvertDelegate toUnit) {
                Name = name;
                Format = format;
                _parse = parse;
                _toBase = toBase;
                _toUnit = toUnit;
            }

            public void ToUnit(Item item, CultureInfo culture, ref object value) {
                object baseValue;
                if (_parse(value, out baseValue))
                    value = item._toUnit(_toBase(baseValue));
            }

            public override string ToString() {
                return Name;
            }
        }

    }
}
